package airAgent.group5;

import java.util.HashMap;

import airAgent.group5.exceptions.AccountNameNotAvailableException;

import airAgent.group5.ontology.ontology.*;
//import airAgent.group5.ontology.ontology.AirAgentOntology;

import jade.content.lang.Codec;
import jade.content.lang.Codec.*;
import jade.content.lang.sl.*;
import jade.content.*;
import jade.content.onto.*;
import jade.content.onto.basic.*;
//---
import jade.core.AID;
import jade.core.Agent;
import jade.core.behaviours.CyclicBehaviour;
import jade.core.behaviours.SimpleBehaviour;
import jade.domain.DFService;
import jade.domain.FIPAException;
import jade.domain.FIPAAgentManagement.DFAgentDescription;
import jade.domain.FIPAAgentManagement.ServiceDescription;
import jade.lang.acl.ACLMessage;
import jade.lang.acl.MessageTemplate;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Map;

public class Bank extends Agent {

    //airAgent.group5.ontology.ontology.Airline ontoBank = new airAgent.group5.ontology.ontology.Airline();
    //private Ontology ontoBank = AirAgentOntology.getInstance();
    private static String NAME = "BANK";
    private HashMap<String, Account> accounts = new HashMap<String, Account>();
    private Codec codec = new SLCodec();
    private Ontology ontology = AirAgentOntology.getInstance();
    private AID timerRef;
    private boolean hasTimer = false;

    private class SubscribeTimerBehaviour extends CyclicBehaviour {

        private boolean end = false;
        private int tries = 0;

        public void action() {

            // Creates the description for the type of agent to be searched
            DFAgentDescription dfd = new DFAgentDescription();
            ServiceDescription sd = new ServiceDescription();
            sd.setType(AirAgentOntology.TIMER);		
            dfd.addServices(sd);
            if (hasTimer()) {
                removeBehaviour(this);
            } else {
                try {
                    tries++;
                 //   System.out.println(tries + " tries to subscribe timer");
                    // It finds agents of the required type
                    DFAgentDescription[] res = new DFAgentDescription[1];
                    res = DFService.search(myAgent, dfd);
                    if (res.length > 0) {
                        timerRef = (AID) res[0].getName();
                        ACLMessage msg = new ACLMessage(ACLMessage.SUBSCRIBE);
                        msg.addReceiver(timerRef);
                        msg.setLanguage(codec.getName());
                        msg.setOntology(ontology.getName());
                        msg.setProtocol(AirAgentOntology.SUBSCRIBETIMER);
                        SubscribeTimer subscribeTimer = new SubscribeTimer();
                        // As it is an action and the encoding language the SL, it must be wrapped
                        // into an Action
                        Action agAction = new Action(timerRef, subscribeTimer);
                        try {
                            // The ContentManager transforms the java objects into strings
                            getContentManager().fillContent(msg, agAction);
                            send(msg);
                            end = true;
                        //    System.out.println(getLocalName() + ": wants to subscribe to timer");
                        } catch (CodecException ce) {
                            ce.printStackTrace();
                        } catch (OntologyException oe) {
                            oe.printStackTrace();
                        }
                        hasTimer = true;
                        addBehaviour(new ResponseSubscribeTimerBehaviour());
                    }
                } catch (Exception e) {
                }
            }
        }
    }

    private class ResponseSubscribeTimerBehaviour extends SimpleBehaviour {

        @Override
        public void action() {
            ACLMessage msg = receive(MessageTemplate.MatchProtocol(AirAgentOntology.SUBSCRIBETIMER));
            if (msg != null) {
                if (msg.getPerformative() == ACLMessage.AGREE) {
                    hasTimer = true;
                //    System.out.println(getLocalName() + ": Subscribed to Timer");
                }
            } else {
                block();
            }

        }

        @Override
        public boolean done() {
            return hasTimer();
        }
    }

    private class WaitToBehaviour extends SimpleBehaviour {

        private boolean end = false;

        public void action() {
            ACLMessage msg = receive(MessageTemplate.MatchProtocol(AirAgentOntology.STARTSIMULATION));

            if (msg != null) {
    //            System.out.println(getLocalName() + ": Starts...");
                end = true;
            } else {
                block();
            }
        }

        @Override
        public boolean done() {
            return end;
        }
    }

    private class InformDayBehaviour extends CyclicBehaviour {

        public void action() {
            ACLMessage msg = receive(MessageTemplate.MatchProtocol(AirAgentOntology.INFORMDATE));

            if (msg != null) {
            } else {
                block();
            }
        }
    }

    private class EndSimlationBehaviour extends SimpleBehaviour {

        boolean end = false;

        public void action() {
            ACLMessage msg = receive(MessageTemplate.MatchProtocol(AirAgentOntology.ENDOFSIMULATION));

            if (msg != null) {
               // System.out.println(getLocalName() + ": Ending...");
                Iterator it = accounts.entrySet().iterator();
                while (it.hasNext()) {
                    Map.Entry e = (Map.Entry) it.next();
                    Account account = (Account)e.getValue();
                    System.out.println("Performance: "+e.getKey() + " " +account.getValue()+" euros"); // TODO: set format as it's requiered in paper
                }


            } else {
                block();
            }
        }

        @Override
        public boolean done() {
            //for now just call takeDown, but should be remove all behaviour and inform monitor
            if (end == true) {
                takeDown();
            }
            return end;
        }
    }

    private class CreateAccountBehaviour extends CyclicBehaviour {

        public void action() {
            ACLMessage msg = receive(MessageTemplate.MatchProtocol(AirAgentOntology.CREATEACCOUNT));

            if (msg != null) {
                ContentElement ce = null;
                try {
                    if (msg.getPerformative() == ACLMessage.REQUEST) {
                        // The ContentManager transforms the message content (string)
                        // in java objects
                        ce = getContentManager().extractContent(msg);
                        // We expect an action inside the message
                        if (ce instanceof Action) {
                            Action agAction = (Action) ce;
                            Concept conc = agAction.getAction();
                            // If the action is CreateAccount...
                            if (conc instanceof CreateAccount) {
                             //   System.out.println(myAgent.getLocalName() + ": " + (msg.getSender()).getLocalName()
                             //           + " wants an account");
                                ACLMessage reply = msg.createReply();
                                reply.setLanguage(codec.getName());
                                reply.setOntology(ontology.getName());
                                reply.setProtocol(AirAgentOntology.CREATEACCOUNT);
                                // putting ontology back in answer, IS IT CORRECT?
                                getContentManager().fillContent(reply, ce);

                                // A INFORM message is sent
                                try {
                                    createAccount(msg.getSender().getLocalName());
                                    reply.setPerformative(ACLMessage.INFORM);
                                } catch (AccountNameNotAvailableException e) {
                                    reply.setPerformative(ACLMessage.REFUSE);
                                    System.err.println("Account-name already in use. Refuse send, stack:");
                                    e.printStackTrace();
                                }
                                myAgent.send(reply);
                               // System.out.println(myAgent.getLocalName() + ": answer sent -> " + reply.getPerformative());
                            }
                        }
                    }
                } catch (CodecException e) {
                    e.printStackTrace();
                } catch (OntologyException oe) {
                    oe.printStackTrace();
                }

            } else {
                block();
            }
        }
    }

    private class DepositBehaviour extends CyclicBehaviour {

        public void action() {
            ACLMessage msg = receive(MessageTemplate.MatchProtocol(AirAgentOntology.DEPOSIT));

            if (msg != null) {
                ContentElement ce = null;
                try {
                    if (msg.getPerformative() == ACLMessage.REQUEST) {
                        // The ContentManager transforms the message content (string)
                        // in java objects
                        ce = getContentManager().extractContent(msg);
                        // We expect an action inside the message
                        if (ce instanceof Action) {
                            Action agAction = (Action) ce;
                            Concept conc = agAction.getAction();
                            if (conc instanceof Deposit) {
                             //   System.out.println(myAgent.getLocalName() + ": " + (msg.getSender()).getLocalName()
                             //           + " wants to deposit money");

                                ACLMessage reply = msg.createReply();
                                reply.setLanguage(codec.getName());
                                reply.setOntology(ontology.getName());
                                reply.setProtocol(AirAgentOntology.DEPOSIT);
                                // putting ontology back in answer
                                getContentManager().fillContent(reply, ce);

                                // A INFORM message is sent
                                Object key = msg.getSender().getLocalName();
                                if (accounts.containsKey(key)) {
                                    Deposit dep = (Deposit) conc;
                                    float value = dep.getAmount().getValue();
                                    accounts.get(key).deposit(value);
                                    
                                    reply.setPerformative(ACLMessage.INFORM);
                                    /*
                                    System.out.println(myAgent.getLocalName() + ": " + (msg.getSender()).getLocalName()
                                            + " deposited " + value + " in his account");
                                    System.out.println(myAgent.getLocalName() + ": " + (msg.getSender()).getLocalName()
                                            + " has saldo " + accounts.get(key).getValue());
                                     * 
                                     */
                                } else {
                                    // exception instead of if/else ???
                                    reply.setPerformative(ACLMessage.REFUSE);
                                    System.out.println(myAgent.getLocalName() + ": " + (msg.getSender()).getLocalName()
                                            + " doesn't have account");
                                }
                                myAgent.send(reply);
                            }
                        }
                    }
                } catch (CodecException e) {
                    e.printStackTrace();
                } catch (OntologyException oe) {
                    oe.printStackTrace();
                }

            } else {
                block();
            }
        }
    }

    private class WithdrawBehaviour extends CyclicBehaviour {

        public void action() {
            ACLMessage msg = receive(MessageTemplate.MatchProtocol(AirAgentOntology.WITHDRAWN));

            if (msg != null) {
                ContentElement ce = null;
                try {
                    if (msg.getPerformative() == ACLMessage.REQUEST) {
                        // The ContentManager transforms the message content (string)
                        // in java objects
                        ce = getContentManager().extractContent(msg);
                        // We expect an action inside the message
                        if (ce instanceof Action) {
                            Action agAction = (Action) ce;
                            Concept conc = agAction.getAction();
                            if (conc instanceof Withdrawn) {
                              //  System.out.println(myAgent.getLocalName() + ": " + (msg.getSender()).getLocalName()
                              //          + " wants to withdraw money");

                                ACLMessage reply = msg.createReply();
                                reply.setLanguage(codec.getName());
                                reply.setOntology(ontology.getName());
                                reply.setProtocol(AirAgentOntology.WITHDRAWN);
                                // putting ontology back in answer
                                getContentManager().fillContent(reply, ce);

                                // A INFORM message is sent
                                Object key = msg.getSender().getLocalName();
                                if (accounts.containsKey(key)) {
                                    Withdrawn wit = (Withdrawn) conc;
                                    float value = wit.getAmount().getValue();
                                    accounts.get(key).withdraw(value);
                                    reply.setPerformative(ACLMessage.INFORM);
                                    /*
                                    System.out.println(myAgent.getLocalName() + ": " + (msg.getSender()).getLocalName()
                                            + " withdrawed " + value + " in his account");
                                    System.out.println(myAgent.getLocalName() + ": " + (msg.getSender()).getLocalName()
                                            + " has saldo " + accounts.get(key).getValue());
                                     *
                                     */
                                } else {
                                    // exception instead of if/else ???
                                    reply.setPerformative(ACLMessage.REFUSE);
                                    System.out.println(myAgent.getLocalName() + ": " + (msg.getSender()).getLocalName()
                                            + " doesn't have account");
                                }
                                myAgent.send(reply);
                           //     System.out.println(myAgent.getLocalName() + ": answer sent -> " + reply.getPerformative());
                            }
                        }
                    }
                } catch (CodecException e) {
                    e.printStackTrace();
                } catch (OntologyException oe) {
                    oe.printStackTrace();
                }

            } else {
                block();
            }
        }
    }

    protected void setup() {
    //    System.out.println(getLocalName() + ": HAS ENTERED");
        getContentManager().registerLanguage(codec);
        getContentManager().registerOntology(ontology);
        addBehaviour(new CreateAccountBehaviour());
        addBehaviour(new DepositBehaviour());
        addBehaviour(new WithdrawBehaviour());
        addBehaviour(new SubscribeTimerBehaviour());
        addBehaviour(new WaitToBehaviour());
        addBehaviour(new InformDayBehaviour());
        addBehaviour(new EndSimlationBehaviour());
        try {
            // Creates its own description
            DFAgentDescription dfd = new DFAgentDescription();
            ServiceDescription sd = new ServiceDescription();
            sd.setName(this.getName());
            sd.setType(NAME);		// SHOULD USE THE ONTOLOGY (ontoBank)
            dfd.addServices(sd);
            // Registers its description in the DF
            DFService.register(this, dfd);
         //   System.out.println(getLocalName() + ": registered in the DF");
        } catch (FIPAException e) {
            e.printStackTrace();
        }

//		BEHAVIOURS ****************************************************************

        // Adds a behavior to create a new account.
        //
        /*
        addBehaviour(new CyclicBehaviour(this) {

        public void action() {
        ACLMessage msg = receive(MessageTemplate.and(MessageTemplate.MatchLanguage(codec.getName()),
        MessageTemplate.MatchOntology(ontology.getName())));
        if (msg != null) {
        System.out.println(myAgent.getLocalName() + ": positive contact with " + msg.getSender().getLocalName());
        try {
        ContentElement ce = null;
        if (msg.getPerformative() == ACLMessage.REQUEST) {
        // The ContentManager transforms the message content (string)
        // in java objects
        ce = getContentManager().extractContent(msg);
        // We expect an action inside the message
        if (ce instanceof Action) {
        Action agAction = (Action) ce;
        Concept conc = agAction.getAction();
        // If the action is CreateAccount...
        if (conc instanceof CreateAccount) {
        System.out.println(myAgent.getLocalName() + ": " + (msg.getSender()).getLocalName()
        + " wants an account");
        ACLMessage reply = msg.createReply();
        reply.setLanguage(codec.getName());
        reply.setOntology(ontology.getName());
        reply.setProtocol(AirAgentOntology.CREATEACCOUNT);
        // putting ontology back in answer, IS IT CORRECT?
        getContentManager().fillContent(reply, ce);

        // A INFORM message is sent
        try {
        createAccount(msg.getSender().getLocalName());
        reply.setPerformative(ACLMessage.INFORM);
        } catch (AccountNameNotAvailableException e) {
        reply.setPerformative(ACLMessage.REFUSE);
        System.err.println("Account-name already in use. Refuse send, stack:");
        e.printStackTrace();
        }
        myAgent.send(reply);
        System.out.println(myAgent.getLocalName() + ": answer sent -> " + reply.getPerformative());

        // If the action is Deposit...
        } else if (conc instanceof Deposit) {
        System.out.println(myAgent.getLocalName() + ": " + (msg.getSender()).getLocalName()
        + " wants to deposit money");

        ACLMessage reply = msg.createReply();
        reply.setLanguage(codec.getName());
        reply.setOntology(ontology.getName());
        reply.setProtocol(AirAgentOntology.DEPOSIT);
        // putting ontology back in answer
        reply.setProtocol(AirAgentOntology.DEPOSIT);
        getContentManager().fillContent(reply, ce);

        // A INFORM message is sent
        Object key = msg.getSender().getLocalName();
        if (accounts.containsKey(key)) {
        Deposit dep = (Deposit) conc;
        float value = dep.getAmount().getValue();
        accounts.get(key).deposit(value);
        reply.setPerformative(ACLMessage.INFORM);
        System.out.println(myAgent.getLocalName() + ": " + (msg.getSender()).getLocalName()
        + " deposited " + value + " in his account");
        System.out.println(myAgent.getLocalName() + ": " + (msg.getSender()).getLocalName()
        + " has saldo " + accounts.get(key).getValue());
        } else {
        // exception instead of if/else ???
        reply.setPerformative(ACLMessage.REFUSE);
        System.out.println(myAgent.getLocalName() + ": " + (msg.getSender()).getLocalName()
        + " doesn't have account");
        }
        myAgent.send(reply);
        System.out.println(myAgent.getLocalName() + ": answer sent -> " + reply.getPerformative());
        } else if (conc instanceof Withdrawn) {
        System.out.println(myAgent.getLocalName() + ": " + (msg.getSender()).getLocalName()
        + " wants to withdraw money");

        ACLMessage reply = msg.createReply();
        reply.setLanguage(codec.getName());
        reply.setOntology(ontology.getName());
        reply.setProtocol(AirAgentOntology.WITHDRAWN);
        // putting ontology back in answer
        getContentManager().fillContent(reply, ce);

        // A INFORM message is sent
        Object key = msg.getSender().getLocalName();
        if (accounts.containsKey(key)) {
        Withdrawn wit = (Withdrawn) conc;
        float value = wit.getAmount().getValue();
        accounts.get(key).withdraw(value);
        reply.setPerformative(ACLMessage.INFORM);
        System.out.println(myAgent.getLocalName() + ": " + (msg.getSender()).getLocalName()
        + " withdrawed " + value + " in his account");
        System.out.println(myAgent.getLocalName() + ": " + (msg.getSender()).getLocalName()
        + " has saldo " + accounts.get(key).getValue());
        } else {
        // exception instead of if/else ???
        reply.setPerformative(ACLMessage.REFUSE);
        System.out.println(myAgent.getLocalName() + ": " + (msg.getSender()).getLocalName()
        + " doesn't have account");
        }
        myAgent.send(reply);
        System.out.println(myAgent.getLocalName() + ": answer sent -> " + reply.getPerformative());
        }
        }
        } else {
        // If what is received is not understood
        System.out.println(myAgent.getLocalName() + ": received unknown message from " + (msg.getSender()).getLocalName());
        ACLMessage reply = msg.createReply();
        //A NOT_UNDERSTOOD is sent
        reply.setPerformative(ACLMessage.NOT_UNDERSTOOD);
        myAgent.send(reply);
        System.out.println(myAgent.getLocalName() + ": Perplexity sent");
        }
        } catch (CodecException e) {
        e.printStackTrace();
        } catch (OntologyException oe) {
        oe.printStackTrace();
        }
        } else {
        block();
        }
        }
        });
         */
    }

    private void createAccount(String accountHolder) throws AccountNameNotAvailableException {
        if (!accounts.containsKey(accountHolder)) {
            accounts.put(accountHolder, new Account());
        } else {
            throw new AccountNameNotAvailableException("The account-name is already in use!");
        }
    }

    public boolean hasTimer() {
        return hasTimer;
    }
}
